package game.evolver;

import java.util.ArrayList;

import java.util.Date;

import game.CardName;
import game.Game;
import org.jgap.*;
import org.jgap.impl.*;
import static game.CardName.*;


public class DeckEvolverMain {

	public static final int MAX_NON_BASIC_LAND = 6;
	public static final int MAX_BASIC_LAND = 30;
	/**
	 * The total number of times we'll let the population evolve.
	 */
	private static final int MAX_ALLOWED_EVOLUTIONS = 200;
	private static final int POPULATION_SIZE = 200; // a few thousand seems better

	public static final CardName[] cardNames = {MOUNTAIN, GOBLIN_GUIDE, LIGHTNING_BOLT, BALL_LIGHTNING, HELLS_THUNDER, HELLSPARK_ELEMENTAL, TEETERING_PEAKS, ARID_MESA, SCALDING_TARN, SEARING_BLAZE, JACKAL_FAMILIAR, BURST_LIGHTNING, FLAME_JAVELIN};
	
	// TODO: the display will be off because of sac lands - fix this, and fix sac lands everywhere else they are referenced
	
	public static void findBestDeck() throws Exception {

		System.out.println("Starting search for best deck");
		// Start with a DefaultConfiguration, which comes setup with the
		// most common settings.
		// -------------------------------------------------------------
		Configuration conf = new DeckEvolverConfiguration();
		conf.setPreservFittestIndividual(true);
		// Set the fitness function we want to use. We construct it with
		// the target volume passed in to this method.
		// ---------------------------------------------------------
		FitnessFunction myFunc = new DeckEvolverFitnessFunction();
		conf.setFitnessFunction(myFunc);
		// Now we need to tell the Configuration object how we want our
		// Chromosomes to be setup. We do that by actually creating a
		// sample Chromosome and then setting it on the Configuration
		// object. As mentioned earlier, we want our Chromosomes to each
		// have as many genes as there are different items available. We want the
		// values (alleles) of those genes to be integers, which represent
		// how many items of that type we have. We therefore use the
		// IntegerGene class to represent each of the genes. That class
		// also lets us specify a lower and upper bound, which we set
		// to senseful values (i.e. maximum possible) for each item type.
		// --------------------------------------------------------------
		Gene[] sampleGenes = new Gene[cardNames.length];
		sampleGenes[0] = new IntegerGene(conf, 0, 30); // between 0 and 30 mountains
		for (int i = 1; i < cardNames.length; i++) {
			sampleGenes[i] = new IntegerGene(conf, 0, MAX_NON_BASIC_LAND); // can have between 0 and 12 of other kinds of cards card (TODO: limit to 4 except sac lands which can be 8)
		}
		IChromosome sampleChromosome = new Chromosome(conf, sampleGenes);
		conf.setSampleChromosome(sampleChromosome);
		// Finally, we need to tell the Configuration object how many
		// Chromosomes we want in our population. The more Chromosomes,
		// the larger number of potential solutions (which is good for
		// finding the answer), but the longer it will take to evolve
		// the population (which could be seen as bad).
		// ------------------------------------------------------------
		conf.setPopulationSize(POPULATION_SIZE);
		// Create random initial population of Chromosomes.



		// ALASDAIR TO DO: Change this so that the population contains only decks with 60 cards (just call add random card repeatedly)


		// ALASDAIR TO DO: also print population size on each iteration

		//Genotype population = Genotype.randomInitialGenotype(conf);

		Genotype population = makeRandomPopulationOF60CardDecks(conf);


		// Evolve the population. Since we don't know what the best answer
		// is going to be, we just evolve the max number of times.
		// ---------------------------------------------------------------
		for (int i = 0; i < MAX_ALLOWED_EVOLUTIONS; i++) {
			System.out.println("Evolution: " + i);
			population.evolve();
			//if (i%10==0) {
			printBestSolutionSoFar(population);
			//}
		}

		// print best solution so far
		printBestSolutionSoFar(population);
	}

	private static Genotype makeRandomPopulationOF60CardDecks(Configuration conf) throws InvalidConfigurationException {
		Genotype genotype = null;
		// Completely initialize the population with custom code.
		// Notice that we assign the double number of Genes to
		// each other Chromosome.
		// ------------------------------------------------------
		int populationSize = conf.getPopulationSize();
		Population pop = new Population(conf, populationSize);
		for (int i = 0; i < populationSize; i++) {

			IChromosome sampleChromosome = conf.getSampleChromosome();
			Gene[] sampleGenes = sampleChromosome.getGenes();
			Gene[] newGenes = new Gene[sampleGenes.length];
			RandomGenerator generator = conf.getRandomGenerator();

			if (newGenes[0] == null) {
				newGenes[0] = new IntegerGene(conf, 0, MAX_BASIC_LAND);
				Integer allele = 0;
				newGenes[0].setAllele(allele);		
			}
			for (int p = 1; p < newGenes.length; p++) {
				if (newGenes[p] == null) {
					newGenes[p] = new IntegerGene(conf, 0, MAX_NON_BASIC_LAND);
					Integer allele = 0;
					newGenes[p].setAllele(allele);
				}
			}
			
			// increment a random gene 60 times (one for each card in deck, paying attention to maximums
			int count = 0;
			while (count < 60) {
				ArrayList<Integer> cardTypesThatAreAtMax = new ArrayList<Integer>();
				int cardType = (int)(DeckEvolverMain.cardNames.length * Math.random()); // equal chance of each type of card
				if (cardTypesThatAreAtMax.contains(cardType)) {
					continue;
				}
				int allele = 0;
				if (newGenes[cardType] != null) {
					allele = ((Integer)newGenes[cardType].getAllele());
				}
				if ((cardType!=0 && allele < DeckEvolverMain.MAX_NON_BASIC_LAND) || (cardType==0 && allele< DeckEvolverMain.MAX_BASIC_LAND)) {
					newGenes[cardType].setAllele(++allele);
					count++;
				} else {
					// remove this card type from consideration (by adding it to a temporary list)
					cardTypesThatAreAtMax.add(cardType);
					// then try again
					continue;
				}
			}	        	
			IChromosome chrom = Chromosome.randomInitialChromosome(conf);
			chrom.setGenes(newGenes);
			pop.addChromosome(chrom);
		}

		// Now we need to construct the Genotype. This could otherwise be
		// accomplished more easily by writing
		// "Genotype genotype = Genotype.randomInitialGenotype(...)"
		genotype = new Genotype(conf, pop);
		return genotype;
	}

	private static void printBestSolutionSoFar(Genotype population) {
		IChromosome bestSolutionSoFar = population.getFittestChromosome();
		System.out.println("The best solution has a fitness value of " +
				bestSolutionSoFar.getFitnessValue());
		System.out.println("It contained the following: ");
		int count;
		int totalCards = 0;
		for (int i = 0; i < bestSolutionSoFar.size(); i++) {
			count = ( (Integer) bestSolutionSoFar.getGene(i).getAllele()).intValue();
			if (count > 0) {
				System.out.println("\t " + count + " x " + cardNames[i]);
				totalCards += count;
			}
		}
		System.out.println("\nTotal deck size: " + totalCards);
		
		int[] quantities = new int[cardNames.length];
		for (int i = 0; i < cardNames.length; i++) {
			quantities[i] = ((Integer)bestSolutionSoFar.getGene(i).getAllele()).intValue();
		}
		Game g = new Game();
		double turns = g.playGameSeries(cardNames, quantities);

		System.out.println("Approximate number of turns: " + turns);
		
		Date d = new Date();
		System.out.println("Finished at: "+ d);
		System.out.println("-----------------------------------------------------------------------------");
	}

	/**
	 * Main method. A single command-line argument is expected, which is the
	 * volume to create (in other words, 75 would be equal to 75 ccm).
	 *
	 * @param args first and single element in the array = volume of the knapsack
	 * to fill as a double value
	 *
	 * @author Klaus Meffert
	 * @since 2.3
	 */
	public static void main(String[] args) {
		Date d = new Date();
		System.out.println("Started at: " + d);
		try {
			findBestDeck();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

